package proxyc

import (
	"errors"
	"gitee.com/yysf-xin/dproxy/pkg/app"
	"gitee.com/yysf-xin/dproxy/pkg/db"
	"gitee.com/yysf-xin/dproxy/pkg/model"
	"gitee.com/yysf-xin/dproxy/pkg/svr"
	"gitee.com/yysf-xin/dproxy/pkg/tools"
	"github.com/go-chi/chi/v5"
	"golang.org/x/net/webdav"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"sync"
)

type LocalControl struct {
	DB         *db.Client
	mux        *http.ServeMux
	ServerMap  map[string]*svr.LocalServer
	mapLock    sync.RWMutex
	idMap      map[uint]*db.LocalServer
	idMapLock  sync.RWMutex
	muxMap     map[*db.LocalServer]http.Handler
	muxMapLock sync.RWMutex
	services   *app.Services
}

func (l *LocalControl) Start() (err error) {
	if l.DB == nil {
		return
	}
	l.mux = http.NewServeMux()
	var list db.LocalServerList
	list.ListByEnabled(l.DB.Db)

	for i := range list {
		err = l.StartServe(list[i])
	}
	for _, v := range l.ServerMap {
		err = v.Start()
		if err != nil {
			l.services.AddService(v)
		}
	}
	return
}

func (l *LocalControl) Stop() {
	for _, v := range l.ServerMap {
		tools.Log.Println("stop", v)
		l.services.RemoveService(v)
		v.Stop()
	}
	l.ServerMap = make(map[string]*svr.LocalServer)
}

func (l *LocalControl) reload(http.ResponseWriter, *http.Request, interface{}) (v interface{}, err error) {
	l.Stop()
	err = l.Start()
	return
}
func NewLocalControl(DB *db.Client, services *app.Services) *LocalControl {
	l := &LocalControl{
		DB:        DB,
		ServerMap: make(map[string]*svr.LocalServer),
		idMap:     make(map[uint]*db.LocalServer),
		muxMap:    make(map[*db.LocalServer]http.Handler),
		services:  services,
	}
	_ = l.Start()
	return l
}
func (l *LocalControl) InitRoute(router chi.Router) {
	router.Get("/", app.Json(nil, l.list))
	router.Route("/{id}/", func(r chi.Router) {
		r.Post("/enable", app.JsonHttp(func() interface{} {
			return &model.State{}
		}, l.handlerEnable))
	})

	router.Delete("/{id}", app.JsonHttp(nil, l.deleteServer))

	//router.Route("/local", func(r chi.Router) {
	//	r.Get("/reload", app.JsonHttp(nil, l.reload))
	//})
}
func (l *LocalControl) list(r interface{}) (rs interface{}, err error) {
	var list []*db.LocalServer
	if err = l.DB.Db.Find(&list).Error; err != nil {
		return
	}
	l.idMapLock.RLock()
	l.mapLock.RLock()
	for i := range list {
		if l.idMap[list[i].Id] == nil {
			list[i].Status = 0
		} else if list[i].Host != "" {
			s := l.ServerMap[list[i].Host]
			if s == nil {
				list[i].Status = 0
			} else {
				list[i].Status = s.Status
			}
		} else {
			list[i].Status = 1
		}
	}
	l.idMapLock.RUnlock()
	l.mapLock.RUnlock()
	rs = list
	return
}

func (l *LocalControl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	l.mux.ServeHTTP(w, r)
}
func fixPath(path string) string {
	if len(path) > 4 {
		if strings.HasSuffix(path[:4], ":/") {
			return path[1:]
		}
	}
	return path
}
func (l *LocalControl) StartServe(p *db.LocalServer) (err error) {
	if p.TargetURL == nil {
		p.TargetURL, _ = url.Parse(p.Target)
	}
	if p.TargetURL == nil {
		err = errors.New("error target url " + p.Target)
		return
	}
	var mHandler http.Handler
	if p.TargetURL.Scheme == "file" {
		mHandler = http.FileServer(http.Dir(fixPath(p.TargetURL.Path)))
		if p.Path != "" && p.Path != "/" {
			if strings.HasSuffix(p.Path, "/") {
				mHandler = http.StripPrefix(p.Path, mHandler)
			}
		}
	} else if strings.HasPrefix(p.TargetURL.Scheme, "http") {
		mHandler = httputil.NewSingleHostReverseProxy(p.TargetURL)
		mHandler = &httputil.ReverseProxy{
			Director:  p.Director,
			Transport: http.DefaultTransport,
		}
	} else if p.TargetURL.Scheme == "webdav" {
		mHandler = &webdav.Handler{
			Prefix:     p.Path,
			FileSystem: webdav.Dir(fixPath(p.TargetURL.Path)),
			LockSystem: webdav.NewMemLS(),
		}
	} else {
		err = errors.New("error target url " + p.Target)
		return
	}

	if mHandler != nil {

		defer func() {
			switch r := recover().(type) {
			case nil:
				break
			case error:
				err = r
			case string:
				err = errors.New(r)
			default:
				tools.Log.Printf("unexpected panic value: %T(%v)\n", r, r)
			}
		}()

		if p.Host == "" {
			l.mux.Handle(p.Path, mHandler)
			l.muxMap[p] = mHandler
		} else {
			l.mapLock.RLock()
			s := l.ServerMap[p.Host]
			l.mapLock.RUnlock()
			if s == nil {
				s = &svr.LocalServer{
					Host:   p.Host,
					MuxMap: make(map[*db.LocalServer]http.Handler),
					Mux:    http.NewServeMux(),
					Name:   strconv.Itoa(int(p.Id)) + "@LOCAL",
				}
				l.mapLock.Lock()
				l.ServerMap[p.Host] = s
				l.mapLock.Unlock()
			}
			s.Mux.Handle(p.Path, mHandler)
			s.LocalLock.Lock()
			s.MuxMap[p] = mHandler
			s.LocalLock.Unlock()
		}
	}

	if err == nil {
		l.idMap[p.Id] = p
	}

	return
}
func (l *LocalControl) handlerEnable(w http.ResponseWriter, r *http.Request, i interface{}) (rt interface{}, err error) {
	id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 64)
	if err != nil {
		return
	}
	var p db.LocalServer
	err = l.DB.Db.First(&p, id).Error
	if err != nil {
		return
	}
	if i.(*model.State).Status == 1 {
		l.idMapLock.RLock()
		old := l.idMap[p.Id]
		l.idMapLock.RUnlock()

		if old != nil {
			err = l.StopServe(old)
		}
		if err == nil {
			l.mapLock.RLock()
			s := l.ServerMap[p.Host]
			l.mapLock.RUnlock()
			err = l.StartServe(&p)

			if err == nil {
				l.mapLock.RLock()
				s1 := l.ServerMap[p.Host]
				l.mapLock.RUnlock()
				if s == nil && s1 != nil {
					err = s1.Start()
				}
			}
		}
	} else {
		err = l.StopServe(&p)
	}
	return nil, err
}

func (l *LocalControl) StopServe(p *db.LocalServer) (err error) {
	l.idMapLock.RLock()
	old := l.idMap[p.Id]
	l.idMapLock.RUnlock()

	if old == nil {
		return
	}
	l.idMapLock.Lock()
	delete(l.idMap, p.Id)
	l.idMapLock.Unlock()

	l.mapLock.RLock()
	s := l.ServerMap[old.Host]
	l.mapLock.RUnlock()

	if s != nil {

		s.LocalLock.Lock()
		delete(s.MuxMap, p)
		s.LocalLock.Unlock()

		ml := len(s.MuxMap)
		if ml == 1 {
			s.Stop()
			l.mapLock.Lock()
			delete(l.ServerMap, old.Host)
			l.mapLock.Unlock()
		} else {
			s.InitMux()
		}
	} else {
		l.muxMapLock.Lock()
		delete(l.muxMap, old)
		l.muxMapLock.Unlock()

		mux := http.NewServeMux()
		for server := range l.muxMap {
			mux.Handle(server.Path, l.muxMap[server])
		}
		l.mux = mux
	}
	return

}

func (l *LocalControl) deleteServer(writer http.ResponseWriter, r *http.Request, i interface{}) (rs interface{}, err error) {
	id := chi.URLParam(r, "id")
	Id, err := strconv.ParseUint(id, 10, 64)
	if err != nil {
		return
	}
	l.idMapLock.RLock()
	old := l.idMap[uint(Id)]
	l.idMapLock.RUnlock()

	if old != nil {
		err = l.StopServe(old)
	}
	if err == nil {
		err = l.DB.Db.Delete(&l.DB.Models.LocalHttp, id).Error
	}
	return
}
